﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using SecureConnection.Utils;


namespace RemoteCode
{
    /* Thanks to DragonHunter for showing me his idea for custom and faster Serialization! */
    internal class SerializationHelper
    {
        private static BinaryFormatter bf = new BinaryFormatter();
        static object sync = new object();
        static SerializationHelper()
        {
        }

        //TODO: Add more types -.-

        public static byte[] Serialize(object obj)
        {
            lock (sync)
            {
                ByteArrayWriter pw = new ByteArrayWriter();
                Type t = obj.GetType();
                if (t == typeof(string))
                {
                    pw.WriteByte(1);
                    pw.WriteString((string)obj);
                }
                else if (t == typeof(int))
                {
                    pw.WriteByte(2);
                    pw.WriteInt((int)obj);
                }
                else if (t == typeof(byte))
                {
                    pw.WriteByte(3);
                    pw.WriteByte((byte)obj);
                }
                else if (t == typeof(byte[]))
                {
                    pw.WriteByte(4);
                    pw.WriteBytes((byte[])obj);
                }
                else if (t == typeof(long))
                {
                    pw.WriteByte(5);
                    pw.WriteLong((long)obj);
                }
                else if (t == typeof(short))
                {
                    pw.WriteByte(6);
                    pw.WriteShort((short)obj);
                }
                else if (t == typeof(object[]))
                {
                    pw.WriteByte(7);
                    pw.WriteUShort((ushort)((object[])obj).Length);

                    byte[] buffer = null;
                    for (int i = 0; i < ((object[])obj).Length; i++)
                    {
                        buffer = Serialize(((object[])obj)[i]);

                        pw.WriteInt(buffer.Length);
                        pw.WriteBytes(buffer);
                    }
                }
                else if (t == typeof(Bitmap))
                {
                    Bitmap bmp = (Bitmap)obj;
                    BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);

                    int PixelSize = Math.Abs(data.Stride) / data.Width;
                    int TotalSize = PixelSize * data.Width * data.Height;

                    byte[] buffer = new byte[TotalSize + 12];
                    Buffer.BlockCopy(BitConverter.GetBytes(data.Width), 0, buffer, 0, 4);
                    Buffer.BlockCopy(BitConverter.GetBytes(data.Height), 0, buffer, 4, 4);
                    Buffer.BlockCopy(BitConverter.GetBytes((int)data.PixelFormat), 0, buffer, 8, 4);
                    Marshal.Copy(data.Scan0, buffer, 12, TotalSize);

                    bmp.UnlockBits(data);

                    pw.WriteByte(8);
                    pw.WriteBytes(buffer);
                }
                else
                {
                    pw.WriteByte(0);

                    MemoryStream m = new MemoryStream();
                    bf.Serialize(m, obj);

                    byte[] buffer = m.ToArray();

                    m.Close();
                    m.Dispose();

                    pw.WriteBytes(buffer);
                }

                return pw.ToByteArray();
            }
        }

        public static object Deserialize(byte[] b)
        {
            lock (sync)
            {
                object buffer = null;

                ByteArrayReader pr = new ByteArrayReader(b);
                switch (pr.ReadByte())
                {
                    case 0:
                        MemoryStream m = new MemoryStream(pr.ReadBytes(pr.Data.Length - pr.Offset));

                        buffer = bf.Deserialize(m);

                        m.Close();
                        m.Dispose();
                        break;
                    case 1:
                        buffer = pr.ReadString();
                        break;
                    case 2:
                        buffer = pr.ReadInt();
                        break;
                    case 3:
                        buffer = pr.ReadByte();
                        break;
                    case 4:
                        buffer = pr.ReadBytes(pr.Data.Length - pr.Offset);
                        break;
                    case 5:
                        buffer = pr.ReadLong();
                        break;
                    case 6:
                        buffer = pr.ReadShort();
                        break;
                    case 7:
                        buffer = new object[pr.ReadUShort()];
                        for (int i = 0; i < ((object[])buffer).Length; i++)
                            ((object[])buffer)[i] = Deserialize(pr.ReadBytes(pr.ReadInt()));
                        break;
                    case 8:
                        Bitmap bmp = new Bitmap(pr.ReadInt(), pr.ReadInt(), (PixelFormat)pr.ReadInt());

                        BitmapData bitmapData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
                        Marshal.Copy(b, 12, bitmapData.Scan0, b.Length - 12);
                        bmp.UnlockBits(bitmapData);

                        buffer = bmp;
                        break;
                }
                return buffer;
            }
        }

        public static bool Serializable(object[] objects)
        {
            foreach (object obj in objects)
                if (obj == null || !obj.GetType().IsSerializable)
                    return false;
            return true;
        }
       
        public static void SerializeArray(object[] array, ref ByteArrayWriter bw)
        {
            if (!Serializable(array))
                throw new Exception("Parameter array not serialzable");

            if (array == null || array.Length == 0)
            {
                bw.WriteUShort(0);
                return;
            }

            bw.WriteUShort((ushort)array.Length);

            byte[] buffer;
            foreach (object obj in array)
            {
                buffer = Serialize(obj);

                bw.WriteInt(buffer.Length);
                bw.WriteBytes(buffer);
            }
            buffer = null;
        }
        public static object[] DeserializeArray(ref ByteArrayReader br)
        {
            ushort length = br.ReadUShort();
            object[] array = new object[length];

            for (ushort i = 0; i < length; i++)
                array[i] = Deserialize(br.ReadBytes(br.ReadInt()));

            return array;
        }
    }
}
